import { Prefab, assetManager, resources } from "cc";

export class Res{

    private __debug_progress_arr: Array<any>;

    /**记录远程文件集合 */
    private remote_paths:{[k:string]: string} = {}
    private local_paths:Array<{path: string}>;
    /**记录预制体 */
    private static prefabs:{[k:string]: Prefab} = {}
    /**资源总数 */
    private total = 0;
    /**记录已加载的数量 */
    private loaded_count = 0;

    private _progress = 0;
    /**当前进度 */
    public get progress() {
        return this._progress;
    }
    public set progress(value) {
        // this.__debug_progress_arr.push(value / this.total);// 调试查看加载进度是否正常
        this._progress = value;
        this.mOnLoadProgress(value / this.total);
    }

    /**加载进度回调 */
    private mOnLoadProgress: Function;
    /**加载完成回调 */
    private mOnLoaded: Function;

    public start(remote_paths: {[k:string]: string}, onLoadProgress:((progress: number) => void), onLoaded:Function, onLoadErr: ((err: Error) => boolean)){
        this.load(remote_paths,onLoadProgress, onLoaded);
        this.onLoadError = onLoadErr;
    }

    /**
     * 加载失败了一个资源
     * @param err 失败信息
     * @returns 表示该资源加载失败后是否继续
     */
    public onLoadError: ((err: Error) => boolean) = (err) => true;

    /**
     * 开始加载资源
     * @param paths 远程资源路径
     * @param onLoadProgress 加载进度回调
     * @param onLoaded 加载完成回调
     */
    public load(remote_paths: {[k:string]: string}, onLoadProgress:((progress: number) => void), onLoaded:Function){
        this.__debug_progress_arr = [];
        this.remote_paths = remote_paths || {};             //远程资源
        this.local_paths = resources.getDirWithPath("./");  //获取本地资源
        this.mOnLoadProgress = onLoadProgress;              //加载进度回调
        this.mOnLoaded = onLoaded;                          //加载完成回调
        let keys = Object.keys(remote_paths);               //取出远程资源的key值
        let remote_total = keys.length;                     //远程资源的数量
        let local_total = this.local_paths.length;          //本地资源的数量
        this.total = remote_total + local_total;            //资源总数
        this.progress = 0;                                  //初始化当前进度
        this.loaded_count = 0;                              //初始化已加载的资源数量
        this.loadRemote(keys);                              //先加载远程资源
    }

    /**
     * 加载远程资源
     * @param keys 
     */
    private loadRemote(keys: Array<string>){
        if(keys.length) assetManager.loadRemote(this.remote_paths[keys.pop()], (err) => {
            if(this.loadError(err)) return;
            ++this.progress;
            this.loadRemote(keys);
        });
        else this.loadLocal(this.local_paths);
    }
    /**加载本地资源 */
    private loadLocal(paths: Array<{path: string}>){
        if (paths.length) {
            const path = paths.pop().path;
            resources.load(path, (err, data) => {
                if (this.loadError(err)) return;
                if (data instanceof Prefab) Res.prefabs[path] = data; // 异步获取可能会导致后面的代码执行，因此将预制体缓存起来
                ++this.progress;
                this.loadLocal(paths);
            });
        } else this.loadFinish();
    }

    /**检测是否加载失败 */
    private loadError(err: Error): boolean{
        if(err){
            return !this.onLoadError(err);
        }
        return false;
    }
    /**资源加载完毕 */
    private loadFinish(err?: Error){
        if(this.loadError(err)) return;
        if(this.__debug_progress_arr.length) console.log("progress:", this.__debug_progress_arr);
        this.mOnLoaded()
    }

    /**直接通过路径获取资源 */
    public static get<T>(path:string, callback?:Function):Promise<T>{
        if(this.inst.remote_paths[path]){
            return this.remote(path, callback);
        }else{
            return this.local(path, callback);
        }
    }
    /**
     * 获取本地资源
     * @param path 路径
     * @param callback 加载完成回调
     * @example
     * let font = await Res.local<TTFont>("arial")
     * label.font = font;
     * ------------------
     * Res.local<TTFont>("arial",(font)=>{
     *      label.font = font;
     * })
     */
    public static local<T>(path:string, callback?:Function):Promise<T>{
        if(callback){
            resources.load(path,(err, data)=>{
                if(err){
                    console.error("Res.local error:", err);
                }else{
                    callback(data);
                }
                
            })
        }else{
            return new Promise<T>((resolve, reject)=>{
                resources.load(path,(err:any,data)=>{
                    if(err){
                        console.error("Res.local error:", err);   
                    }else{
                        resolve(data as T);
                    }
                });
            });
        }
        return null
    }

    /**
     * 获取远程资源
     * @param key 键值/路径
     * @param callback 加载完成回调
     * @example
     * let font = await Res.remote<TTFont>("arial")
     * label.font = font;
     * ------------------
     * Res.remote<TTFont>("arial",(font)=>{
     *      label.font = font;
     * })
     */
    public static remote<T>(key:string, callback?:Function):Promise<T>{

        if(callback){
            assetManager.loadRemote(this.inst.remote_paths[key], (err: any, asset: T) => {
                if(err){
                    console.error("Res.remote error:", err);
                }else{
                    callback(asset);
                } 
            });
        }else{
            return new Promise<T>((resolve, reject)=>{
                assetManager.loadRemote(this.inst.remote_paths[key], (err: any, asset: T) => {
                    if(err){
                        console.error("Res.remote error:", err);
                    }else{
                        resolve(asset);
                    } 
                    
                });
            });
        }
        return null
    }

    public static prefab(key: string): Prefab{
        return this.prefabs[key];
    }

    /**
     * 获取Res实例
     * @returns {Res}
     */
    public static get inst():Res {
        if (!this._instance) this._instance = new Res;
        return this._instance;
    }
    private static _instance: Res;
}